Java JavaScript Python C# C C++ Go Kotlin PHP Swift R Ruby TypeScript Scala SQL Perl rust VisualBasic Matlab Julia

Generics → Generic Classes

Generics

Generic Classes

Generic Classes

Generic classes in Java allow you to write classes and methods that can work with different data types without losing type safety. Before generics, you'd often use `Object` as a placeholder, requiring casting which could lead to `ClassCastException` runtime errors. Generics eliminate this risk by allowing you to specify the data type at compile time.

How Generics Work

The core concept is using type parameters within angle brackets `<>` when defining a class or method. These parameters act as placeholders for specific data types that will be provided when you create an instance of the generic class or call the generic method.

Example 1: A Simple Generic Class

Let's create a simple `Box` class that can hold any type of object:
Java Generic Class example public class Box<T> { private T content; public Box(T content) { this.content = content; } public T getContent() { return content; } public static void main(String[] args) { Box<String> stringBox = new Box<>("Hello"); String message = stringBox.getContent(); System.out.println(message); // Output: Hello Box<Integer> integerBox = new Box<>(123); int number = integerBox.getContent(); System.out.println(number); // Output: 123 } }

Output

Hello 123
Here, `T` is a type parameter. It's a placeholder for the actual data type. When you create a `Box`, you specify the type. Notice that we don't need any casting. The compiler knows the type of object inside each `Box` at compile time, ensuring type safety.

Example 2: Generic Methods

Generics aren't limited to classes; you can also have generic methods:
generic method example public class Utils { public static <E> void printArray(E[] array) { for (E element : array) { System.out.println(element); } } public static void main(String[] args) { // main is now directly inside the Utils class Integer[] intArray = {1, 2, 3}; String[] stringArray = {"a", "b", "c"}; Utils.printArray(intArray); Utils.printArray(stringArray); } }

Output

1 2 3 a b c
Here, `<E>` declares a type parameter `E` for the `printArray` method. `E` can be any type. The method can then handle arrays of any type:

Example 3: Multiple Type Parameters

You can use multiple type parameters in a single class or method:
Multiple type parameters example public class Pair<K, V> { // K for key, V for value private K key; private V value; public Pair(K key, V value) { this.key = key; this.value = value; } public K getKey() { return key; } public V getValue() { return value; } public static void main(String[] args) { // Added a main method for demonstration Pair<String, Integer> pair = new Pair<>("apple", 1); System.out.println(pair.getKey() + ": " + pair.getValue()); // Output: apple: 1 Pair<String, String> anotherPair = new Pair<>("Name", "John Doe"); System.out.println(anotherPair.getKey() + ": " + anotherPair.getValue()); // Output: Name: John Doe Pair<Integer, Double> yetAnotherPair = new Pair<>(10, 3.14); System.out.println(yetAnotherPair.getKey() + ": " + yetAnotherPair.getValue()); // Output: 10: 3.14 } }

Output

apple: 1 Name: John Doe 10: 3.14

Example 4: Bounded Type Parameters

Sometimes, you want to restrict the types that can be used as type parameters. This is achieved using *bounded type parameters*:
Bounded Type Parameters example public class NumberBox<T extends Number> { // T must be a subclass of Number private T number; public NumberBox(T number) { this.number = number; } public double getDoubleValue() { return number.doubleValue(); } public static void main(String[] args) { NumberBox<Integer> intBox = new NumberBox<>(10); System.out.println(intBox.getDoubleValue()); // Output: 10.0 NumberBox<Double> doubleBox = new NumberBox<>(3.14); System.out.println(doubleBox.getDoubleValue()); // Output: 3.14 NumberBox<Float> floatBox = new NumberBox<>(2.71f); System.out.println(floatBox.getDoubleValue()); // Output: 2.71 // NumberBox<String> stringBox = new NumberBox<>("hello"); // This will cause a compile-time error } }

Output

10.0 3.14 2.7100000381469727
Here, `T extends Number` ensures that `T` must be a subclass of `Number` (like `Integer`, `Double`, `Float`, etc.). Trying to use a type that doesn't extend `Number` will result in a compile-time error.

Wildcards

Wildcards (`?`) are used when you don't know the exact type but have some constraints: * `<?>`: Unbounded wildcard - means any type. * `<? extends T>`: Upper bounded wildcard - means any type that extends `T` or is a subtype of `T`. * `<? super T>`: Lower bounded wildcard - means any type that is a supertype of `T`. These provide flexibility when working with collections of unknown types. In summary, generics are a powerful feature in Java that enhances type safety, code reusability, and readability by allowing you to write type-safe code that can work with various data types without resorting to unchecked casting. Understanding bounded types and wildcards expands the power and flexibility of generic programming significantly.

Tutorials